Константы и статические переменные
В этой главе мы изучим константы (const) и статические переменные (static) — специальные типы данных в Rust, которые существуют в течение всего времени выполнения программы. Понимание их различий и правильное применение критически важно для написания эффективного кода.
Константы (const)
Константы в Rust — это значения, которые вычисляются во время компиляции и встраиваются непосредственно в код везде, где используются.
Объявление констант
// Константы объявляются на уровне модуля
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159265358979323846;
const APP_NAME: &str = "My Rust Application";
const DEBUG_MODE: bool = true;
fn main() {
println!("Максимальные очки: {}", MAX_POINTS);
println!("Число π: {}", PI);
println!("Имя приложения: {}", APP_NAME);
println!("Режим отладки: {}", DEBUG_MODE);
// Константы можно объявлять внутри функций
const LOCAL_CONSTANT: i32 = 42;
println!("Локальная константа: {}", LOCAL_CONSTANT);
}
Правила для констант
📝 Обязательная типизация
Тип константы должен быть указан явно
const VALUE: i32 = 42;🔒 Всегда неизменяемы
Нельзя использовать mut с константами
// const mut X: i32 = 42; // ❌ Ошибка⏰ Вычисление на этапе компиляции
Значение должно быть известно компилятору
const RESULT: i32 = 10 + 20;🏷️ Именование SCREAMING_SNAKE_CASE
Принятое соглашение для констант
const MAX_BUFFER_SIZE: usize = 8192;Константные выражения
Rust позволяет использовать в константах довольно сложные выражения:
- Арифметические операции
- Побитовые операции
- Массивы и строки
const SECONDS_IN_MINUTE: u32 = 60;
const MINUTES_IN_HOUR: u32 = 60;
const HOURS_IN_DAY: u32 = 24;
// Составные константы
const SECONDS_IN_HOUR: u32 = SECONDS_IN_MINUTE * MINUTES_IN_HOUR;
const SECONDS_IN_DAY: u32 = SECONDS_IN_HOUR * HOURS_IN_DAY;
const MINUTES_IN_DAY: u32 = MINUTES_IN_HOUR * HOURS_IN_DAY;
// Математические вычисления
const CIRCLE_AREA: f64 = PI * 5.0 * 5.0; // Площадь круга с радиусом 5
const SQUARE_ROOT_OF_2: f64 = 1.4142135623730951;
fn main() {
println!("Секунд в часе: {}", SECONDS_IN_HOUR);
println!("Секунд в дне: {}", SECONDS_IN_DAY);
println!("Минут в дне: {}", MINUTES_IN_DAY);
println!("Площадь круга: {}", CIRCLE_AREA);
println!("√2 ≈ {}", SQUARE_ROOT_OF_2);
}
const FLAG_A: u32 = 1 << 0; // 0b00000001
const FLAG_B: u32 = 1 << 1; // 0b00000010
const FLAG_C: u32 = 1 << 2; // 0b00000100
const FLAG_D: u32 = 1 << 3; // 0b00001000
// Комбинации флагов
const FLAGS_AB: u32 = FLAG_A | FLAG_B; // 0b00000011
const FLAGS_ALL: u32 = FLAG_A | FLAG_B | FLAG_C | FLAG_D; // 0b00001111
// Маски
const MASK_LOWER_NIBBLE: u8 = 0x0F; // 0b00001111
const MASK_UPPER_NIBBLE: u8 = 0xF0; // 0b11110000
const MASK_FULL: u8 = MASK_LOWER_NIBBLE | MASK_UPPER_NIBBLE; // 0b11111111
fn main() {
println!("Флаг A: {:08b}", FLAG_A);
println!("Флаг B: {:08b}", FLAG_B);
println!("Флаги A|B: {:08b}", FLAGS_AB);
println!("Все флаги: {:08b}", FLAGS_ALL);
println!("Нижняя тетрада: {:08b}", MASK_LOWER_NIBBLE);
println!("Верхняя тетрада: {:08b}", MASK_UPPER_NIBBLE);
println!("Полная маска: {:08b}", MASK_FULL);
}
const VOWELS: [char; 5] = ['a', 'e', 'i', 'o', 'u'];
const FIBONACCI: [u32; 10] = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
const POWERS_OF_TWO: [u64; 8] = [1, 2, 4, 8, 16, 32, 64, 128];
// Строковые константы
const GREETING: &str = "Привет, мир!";
const ERROR_MESSAGE: &str = "Произошла ошибка";
const VERSION: &str = "1.0.0";
// Байтовые строки
const MAGIC_BYTES: &[u8] = b"RUST";
const HEX_DUMP: &[u8] = b"\x89PNG\r\n\x1a\n"; // PNG заголовок
fn main() {
println!("Гласные: {:?}", VOWELS);
println!("Числа Фибоначчи: {:?}", FIBONACCI);
println!("Степени двойки: {:?}", POWERS_OF_TWO);
println!("Приветствие: {}", GREETING);
println!("Версия: {}", VERSION);
println!("Магические байты: {:?}", MAGIC_BYTES);
println!("Hex дамп: {:?}", HEX_DUMP);
// Доступ к элементам массива
println!("Первая гласная: {}", VOWELS[0]);
println!("10-е число Фибоначчи: {}", FIBONACCI[9]);
}
Константы в различных контекстах
// Глобальные константы модуля
const BUFFER_SIZE: usize = 8192;
const MAX_CONNECTIONS: u32 = 100;
struct Config {
buffer_size: usize,
max_connections: u32,
timeout_seconds: u64,
}
impl Config {
// Константы внутри impl блока
const DEFAULT_TIMEOUT: u64 = 30;
const MAX_TIMEOUT: u64 = 300;
fn new() -> Self {
Self {
buffer_size: BUFFER_SIZE, // Использование глобальной константы
max_connections: MAX_CONNECTIONS,
timeout_seconds: Self::DEFAULT_TIMEOUT, // Использование константы impl
}
}
fn with_timeout(mut self, timeout: u64) -> Self {
// Константа внутри функции
const MIN_TIMEOUT: u64 = 1;
self.timeout_seconds = timeout.clamp(MIN_TIMEOUT, Self::MAX_TIMEOUT);
self
}
}
fn main() {
let config = Config::new()
.with_timeout(60);
println!("Размер буфера: {}", config.buffer_size);
println!("Максимум подключений: {}", config.max_connections);
println!("Таймаут: {} секунд", config.timeout_seconds);
// Константы в match выражениях
const SUCCESS_CODE: i32 = 0;
const ERROR_CODE: i32 = 1;
let result = SUCCESS_CODE;
match result {
SUCCESS_CODE => println!("Успех!"),
ERROR_CODE => println!("Ошибка!"),
_ => println!("Неизвестный код"),
}
}
Статические переменные (static)
Статические переменные имеют фиксированный адрес в памяти и существуют в течение всего времени выполнения программы.
Объявление статических переменных
static GLOBAL_COUNTER: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
static APP_VERSION: &str = "2.1.0";
static MAX_RETRIES: u8 = 3;
static PI_STATIC: f64 = 3.14159265358979323846;
fn main() {
println!("Версия приложения: {}", APP_VERSION);
println!("Максимум повторов: {}", MAX_RETRIES);
println!("π (статическая): {}", PI_STATIC);
// Работа с атомарным счётчиком
let old_value = GLOBAL_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
println!("Счётчик: {} -> {}", old_value, old_value + 1);
// Получение адреса статической переменной
let version_ptr = &APP_VERSION as *const &str;
println!("Адрес APP_VERSION: {:p}", version_ptr);
}
Мутабельные статические переменные
Мутабельные статические переменные небезопасны и требуют использования unsafe блоков!
static mut GLOBAL_STATE: i32 = 0;
static mut MESSAGE: &str = "Начальное сообщение";
// Более безопасная альтернатива с Mutex
use std::sync::Mutex;
static SAFE_COUNTER: Mutex<i32> = Mutex::new(0);
fn main() {
// Небезопасная работа с мутабельной статической переменной
unsafe {
GLOBAL_STATE += 1;
println!("Глобальное состояние: {}", GLOBAL_STATE);
MESSAGE = "Изменённое сообщение";
println!("Сообщение: {}", MESSAGE);
}
// Безопасная работа с Mutex
{
let mut counter = SAFE_COUNTER.lock().unwrap();
*counter += 1;
println!("Безопасный счётчик: {}", *counter);
} // Mutex автоматически освобождается
// Многопоточный доступ к безопасному счётчику
use std::thread;
let handles: Vec<_> = (0..5).map(|i| {
thread::spawn(move || {
let mut counter = SAFE_COUNTER.lock().unwrap();
*counter += 1;
println!("Поток {} увеличил счётчик до {}", i, *counter);
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
}
Ленивая инициализация статических переменных
Для сложных статических переменных можно использовать ленивую инициализацию:
- std::sync::OnceLock
- Внешняя библиотека lazy_static
- std::sync::Once
use std::sync::OnceLock;
use std::collections::HashMap;
// Статическая переменная с ленивой инициализацией
static SETTINGS: OnceLock<HashMap<String, String>> = OnceLock::new();
fn get_settings() -> &'static HashMap<String, String> {
SETTINGS.get_or_init(|| {
let mut map = HashMap::new();
map.insert("database_url".to_string(), "postgres://localhost".to_string());
map.insert("port".to_string(), "8080".to_string());
map.insert("debug".to_string(), "true".to_string());
map
})
}
fn main() {
// Первый доступ инициализирует HashMap
let settings = get_settings();
println!("База данных: {}", settings.get("database_url").unwrap());
println!("Порт: {}", settings.get("port").unwrap());
// Последующие обращения используют уже инициализированное значение
let settings_again = get_settings();
println!("Отладка: {}", settings_again.get("debug").unwrap());
// Проверяем, что это один и тот же объект
println!("Тот же объект: {}",
std::ptr::eq(settings, settings_again));
}
// В Cargo.toml:
// [dependencies]
// lazy_static = "1.4"
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::sync::Mutex;
lazy_static! {
static ref GLOBAL_CONFIG: HashMap<&'static str, &'static str> = {
let mut map = HashMap::new();
map.insert("app_name", "My Rust App");
map.insert("version", "1.0.0");
map.insert("author", "Rust Developer");
map
};
static ref SHARED_VECTOR: Mutex<Vec<i32>> = {
Mutex::new(vec![1, 2, 3, 4, 5])
};
}
fn main() {
// Доступ к неизменяемой ленивой статической переменной
println!("Название приложения: {}", GLOBAL_CONFIG.get("app_name").unwrap());
println!("Версия: {}", GLOBAL_CONFIG.get("version").unwrap());
// Доступ к изменяемой ленивой статической переменной
{
let mut vec = SHARED_VECTOR.lock().unwrap();
vec.push(6);
println!("Вектор: {:?}", *vec);
}
// Из другого потока
use std::thread;
let handle = thread::spawn(|| {
let mut vec = SHARED_VECTOR.lock().unwrap();
vec.push(7);
println!("Из потока: {:?}", *vec);
});
handle.join().unwrap();
}
use std::sync::{Once, ONCE_INIT};
static INIT: Once = ONCE_INIT;
static mut INITIALIZED_VALUE: Option<String> = None;
fn get_initialized_value() -> &'static str {
unsafe {
INIT.call_once(|| {
// Эта функция выполнится только один раз
println!("Инициализация значения...");
INITIALIZED_VALUE = Some("Инициализированное значение".to_string());
});
INITIALIZED_VALUE.as_ref().unwrap()
}
}
fn main() {
println!("Первый вызов:");
println!("{}", get_initialized_value());
println!("Второй вызов:");
println!("{}", get_initialized_value());
println!("Третий вызов:");
println!("{}", get_initialized_value());
// Инициализация произойдёт только при первом вызове
}
Сравнение const и static
Понимание различий между const и static критически важно:
| Аспект | const | static |
|---|---|---|
| Время вычисления | Во время компиляции | При первом обращении |
| Расположение в памяти | Встраивается в код (inlined) | Фиксированный адрес в памяти |
| Мутабельность | Всегда неизменяемы | Могут быть мутабельными (unsafe) |
| Размер бинарного файла | Может увеличить размер | Одна копия в памяти |
| Использование памяти | Копия в каждом месте использования | Одно место в памяти |
| Получение адреса | Невозможно | Возможно |
| Многопоточность | Нет проблем | Требует синхронизации для mut |
Практическое сравнение
const CONST_VALUE: i32 = 42;
static STATIC_VALUE: i32 = 42;
fn main() {
// Нельзя получить адрес константы
// let const_ptr = &CONST_VALUE as *const i32; // ❌ Ошибка в некоторых контекстах
// Можно получить адрес статической переменной
let static_ptr = &STATIC_VALUE as *const i32;
println!("Адрес статической переменной: {:p}", static_ptr);
// Демонстрация inlining константы
let array_const = [CONST_VALUE; 3]; // Значение встроено
let array_static = [STATIC_VALUE; 3]; // Значение загружено из памяти
println!("Массив с константой: {:?}", array_const);
println!("Массив со статической переменной: {:?}", array_static);
// Множественные обращения
for i in 0..5 {
// CONST_VALUE встраивается в каждое место использования
println!("Константа #{}: {}", i, CONST_VALUE);
// STATIC_VALUE загружается из одного места в памяти
println!("Статическая #{}: {}", i, STATIC_VALUE);
}
}
// Функция для демонстрации inlining
fn use_const() -> i32 {
CONST_VALUE * 2 // Компилятор может оптимизировать это в 84
}
fn use_static() -> i32 {
STATIC_VALUE * 2 // Требует загрузки значения из памяти
}
Когда использовать const vs static
Используйте const когда:
// Математические константы
const PI: f64 = 3.14159265358979323846;
const E: f64 = 2.71828182845904523536;
// Конфигурационные значения
const MAX_BUFFER_SIZE: usize = 8192;
const DEFAULT_TIMEOUT: u64 = 30;
// Магические числа
const HTTP_OK: u16 = 200;
const HTTP_NOT_FOUND: u16 = 404;
// Битовые маски
const READ_PERMISSION: u8 = 0b100;
const WRITE_PERMISSION: u8 = 0b010;
const EXECUTE_PERMISSION: u8 = 0b001;
fn main() {
let area = PI * 5.0 * 5.0;
println!("Площадь круга: {}", area);
let permissions = READ_PERMISSION | WRITE_PERMISSION;
println!("Права доступа: {:03b}", permissions);
}
Используйте static когда:
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
// Глобальные счётчики
static REQUEST_COUNTER: AtomicUsize = AtomicUsize::new(0);
// Глобальные настройки (неизменяемые)
static APPLICATION_NAME: &str = "My Web Server";
static BUILD_VERSION: &str = env!("CARGO_PKG_VERSION");
// Разделяемое состояние (с синхронизацией)
static SHARED_DATA: Mutex<Vec<String>> = Mutex::new(Vec::new());
// Большие неизменяемые данные
static LOOKUP_TABLE: [u64; 1000] = [0; 1000]; // Инициализация нулями
fn increment_requests() {
REQUEST_COUNTER.fetch_add(1, Ordering::Relaxed);
}
fn get_request_count() -> usize {
REQUEST_COUNTER.load(Ordering::Relaxed)
}
fn main() {
println!("Приложение: {}", APPLICATION_NAME);
println!("Версия: {}", BUILD_VERSION);
// Симуляция обработки запросов
for _ in 0..5 {
increment_requests();
}
println!("Обработано запросов: {}", get_request_count());
// Работа с разделяемыми данными
{
let mut data = SHARED_DATA.lock().unwrap();
data.push("Новая запись".to_string());
println!("Записей в shared data: {}", data.len());
}
}
Константы времени компиляции
Rust поддерживает вычисления во время компиляции с помощью const fn:
Константные функции
const fn factorial(n: u64) -> u64 {
if n <= 1 {
1
} else {
n * factorial(n - 1)
}
}
const fn fibonacci(n: u32) -> u64 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
const fn is_power_of_two(n: u32) -> bool {
n != 0 && (n & (n - 1)) == 0
}
// Использование const fn в константах
const FACTORIAL_10: u64 = factorial(10);
const FIB_20: u64 = fibonacci(20);
const IS_POWER_OF_TWO_16: bool = is_power_of_two(16);
fn main() {
println!("10! = {}", FACTORIAL_10); // 3628800
println!("fib(20) = {}", FIB_20); // 6765
println!("16 - степень двойки: {}", IS_POWER_OF_TWO_16); // true
// Можно использовать const fn и в runtime
let runtime_factorial = factorial(5);
println!("5! = {}", runtime_factorial); // 120
// Константы в размерах массивов
const ARRAY_SIZE: usize = factorial(4) as usize;
let buffer: [u8; ARRAY_SIZE] = [0; ARRAY_SIZE];
println!("Размер массива: {}", buffer.len()); // 24
}
Ограничения const fn
// ✅ Разрешено в const fn
const fn allowed_operations(x: i32, y: i32) -> i32 {
// Арифметические операции
let sum = x + y;
let product = x * y;
// Условные выражения
let result = if x > y { sum } else { product };
// Циклы
let mut counter = 0;
let mut i = 0;
while i < 10 {
counter += 1;
i += 1;
}
// Match выражения
match result {
0 => counter,
_ => result + counter,
}
}
// ❌ Не разрешено в const fn (на момент написания)
/*
const fn not_allowed() {
// Динамическое выделение памяти
let v = Vec::new(); // ❌
// Вызов не-const функций
println!("Hello"); // ❌
// Работа с указателями (в большинстве случаев)
let ptr = &42 as *const i32; // ❌
// Использование внешних библиотек
std::thread::sleep(std::time::Duration::from_secs(1)); // ❌
}
*/
const COMPUTED_VALUE: i32 = allowed_operations(10, 20);
fn main() {
println!("Вычисленное значение: {}", COMPUTED_VALUE);
}
Практические примеры
Система конфигурации
use std::sync::OnceLock;
use std::collections::HashMap;
// Константы для значений по умолчанию
const DEFAULT_PORT: u16 = 8080;
const DEFAULT_HOST: &str = "127.0.0.1";
const DEFAULT_MAX_CONNECTIONS: u32 = 1000;
const DEFAULT_TIMEOUT_MS: u64 = 5000;
// Константы для ограничений
const MIN_PORT: u16 = 1024;
const MAX_PORT: u16 = 65535;
const MIN_CONNECTIONS: u32 = 1;
const MAX_CONNECTIONS: u32 = 10000;
// Статическая конфигурация с ленивой инициализацией
static CONFIG: OnceLock<AppConfig> = OnceLock::new();
#[derive(Debug)]
struct AppConfig {
host: String,
port: u16,
max_connections: u32,
timeout_ms: u64,
features: HashMap<String, bool>,
}
impl AppConfig {
const fn validate_port(port: u16) -> bool {
port >= MIN_PORT && port <= MAX_PORT
}
const fn validate_connections(connections: u32) -> bool {
connections >= MIN_CONNECTIONS && connections <= MAX_CONNECTIONS
}
fn new() -> Self {
let mut features = HashMap::new();
features.insert("logging".to_string(), true);
features.insert("metrics".to_string(), false);
features.insert("debug".to_string(), cfg!(debug_assertions));
Self {
host: DEFAULT_HOST.to_string(),
port: DEFAULT_PORT,
max_connections: DEFAULT_MAX_CONNECTIONS,
timeout_ms: DEFAULT_TIMEOUT_MS,
features,
}
}
fn load_from_env() -> Self {
let mut config = Self::new();
// Загрузка из переменных окружения
if let Ok(port_str) = std::env::var("APP_PORT") {
if let Ok(port) = port_str.parse::<u16>() {
if Self::validate_port(port) {
config.port = port;
}
}
}
if let Ok(host) = std::env::var("APP_HOST") {
config.host = host;
}
if let Ok(max_conn_str) = std::env::var("APP_MAX_CONNECTIONS") {
if let Ok(max_conn) = max_conn_str.parse::<u32>() {
if Self::validate_connections(max_conn) {
config.max_connections = max_conn;
}
}
}
config
}
}
fn get_config() -> &'static AppConfig {
CONFIG.get_or_init(AppConfig::load_from_env)
}
fn main() {
// Первое обращение инициализирует конфигурацию
let config = get_config();
println!("Конфигурация приложения:");
println!(" Хост: {}", config.host);
println!(" Порт: {}", config.port);
println!(" Макс. соединений: {}", config.max_connections);
println!(" Таймаут: {} мс", config.timeout_ms);
println!(" Функции:");
for (feature, enabled) in &config.features {
println!(" {}: {}", feature, if *enabled { "включена" } else { "отключена" });
}
// Проверка валидации констант
println!("\nВалидация:");
println!(" Порт 80 валиден: {}", AppConfig::validate_port(80));
println!(" Порт 8080 валиден: {}", AppConfig::validate_port(8080));
println!(" 1000000 соединений валидно: {}", AppConfig::validate_connections(1000000));
}
Система метрик
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::{Duration, Instant};
use std::sync::OnceLock;
// Константы для метрик
const METRICS_UPDATE_INTERVAL_MS: u64 = 1000;
const MAX_RESPONSE_TIME_MS: u64 = 30000;
// Атомарные счётчики для метрик
static REQUESTS_TOTAL: AtomicU64 = AtomicU64::new(0);
static REQUESTS_SUCCESS: AtomicU64 = AtomicU64::new(0);
static REQUESTS_ERROR: AtomicU64 = AtomicU64::new(0);
static RESPONSE_TIME_TOTAL_MS: AtomicU64 = AtomicU64::new(0);
static ACTIVE_CONNECTIONS: AtomicUsize = AtomicUsize::new(0);
// Статическая переменная для времени старта
static START_TIME: OnceLock<Instant> = OnceLock::new();
struct Metrics;
impl Metrics {
fn init() {
START_TIME.get_or_init(Instant::now);
}
fn record_request_start() {
REQUESTS_TOTAL.fetch_add(1, Ordering::Relaxed);
ACTIVE_CONNECTIONS.fetch_add(1, Ordering::Relaxed);
}
fn record_request_end(success: bool, duration: Duration) {
ACTIVE_CONNECTIONS.fetch_sub(1, Ordering::Relaxed);
if success {
REQUESTS_SUCCESS.fetch_add(1, Ordering::Relaxed);
} else {
REQUESTS_ERROR.fetch_add(1, Ordering::Relaxed);
}
let duration_ms = duration.as_millis() as u64;
RESPONSE_TIME_TOTAL_MS.fetch_add(duration_ms, Ordering::Relaxed);
}
fn get_stats() -> MetricsSnapshot {
let total = REQUESTS_TOTAL.load(Ordering::Relaxed);
let success = REQUESTS_SUCCESS.load(Ordering::Relaxed);
let error = REQUESTS_ERROR.load(Ordering::Relaxed);
let response_time_total = RESPONSE_TIME_TOTAL_MS.load(Ordering::Relaxed);
let active = ACTIVE_CONNECTIONS.load(Ordering::Relaxed);
let uptime = START_TIME.get()
.map(|start| start.elapsed())
.unwrap_or(Duration::ZERO);
let avg_response_time = if total > 0 {
response_time_total / total
} else {
0
};
MetricsSnapshot {
requests_total: total,
requests_success: success,
requests_error: error,
success_rate: if total > 0 { success as f64 / total as f64 } else { 0.0 },
average_response_time_ms: avg_response_time,
active_connections: active,
uptime_seconds: uptime.as_secs(),
}
}
fn reset() {
REQUESTS_TOTAL.store(0, Ordering::Relaxed);
REQUESTS_SUCCESS.store(0, Ordering::Relaxed);
REQUESTS_ERROR.store(0, Ordering::Relaxed);
RESPONSE_TIME_TOTAL_MS.store(0, Ordering::Relaxed);
// ACTIVE_CONNECTIONS не сбрасываем - она отражает текущее состояние
}
}
#[derive(Debug)]
struct MetricsSnapshot {
requests_total: u64,
requests_success: u64,
requests_error: u64,
success_rate: f64,
average_response_time_ms: u64,
active_connections: usize,
uptime_seconds: u64,
}
// Симуляция обработки запроса
fn simulate_request(id: u32, will_succeed: bool) {
println!("Запрос {} начат", id);
Metrics::record_request_start();
let start = Instant::now();
// Симуляция работы
std::thread::sleep(Duration::from_millis(10 + (id % 100) as u64));
let duration = start.elapsed();
Metrics::record_request_end(will_succeed, duration);
println!("Запрос {} завершён ({})",
id,
if will_succeed { "успешно" } else { "с ошибкой" });
}
fn main() {
Metrics::init();
println!("Запуск системы метрик...\n");
// Симуляция нескольких запросов
use std::thread;
let handles: Vec<_> = (0..10).map(|i| {
thread::spawn(move || {
simulate_request(i, i % 4 != 0); // 75% успешных запросов
})
}).collect();
// Ждём завершения всех запросов
for handle in handles {
handle.join().unwrap();
}
// Получаем и выводим статистику
let stats = Metrics::get_stats();
println!("\n=== МЕТРИКИ ===");
println!("Всего запросов: {}", stats.requests_total);
println!("Успешных запросов: {}", stats.requests_success);
println!("Ошибочных запросов: {}", stats.requests_error);
println!("Коэффициент успеха: {:.1}%", stats.success_rate * 100.0);
println!("Среднее время ответа: {} мс", stats.average_response_time_ms);
println!("Активных соединений: {}", stats.active_connections);
println!("Время работы: {} секунд", stats.uptime_seconds);
// Демонстрация сброса метрик
println!("\nСброс метрик...");
Metrics::reset();
let reset_stats = Metrics::get_stats();
println!("После сброса - всего запросов: {}", reset_stats.requests_total);
}
Конфигурация компиляции
// Константы, зависящие от режима сборки
const LOG_LEVEL: &str = if cfg!(debug_assertions) {
"DEBUG"
} else {
"INFO"
};
const BUFFER_SIZE: usize = if cfg!(debug_assertions) {
1024 // Меньший буфер в debug режиме для тестирования
} else {
8192 // Больший буфер в release режиме для производительности
};
const ENABLE_PROFILING: bool = cfg!(debug_assertions);
const ENABLE_METRICS: bool = true;
const ENABLE_TRACING: bool = cfg!(feature = "tracing");
// Константы для различных платформ
const MAX_PATH_LENGTH: usize = if cfg!(target_os = "windows") {
260
} else if cfg!(target_os = "linux") {
4096
} else {
1024
};
const PATH_SEPARATOR: char = if cfg!(target_os = "windows") {
'\\'
} else {
'/'
};
// Архитектурно-зависимые константы
const WORD_SIZE: usize = std::mem::size_of::<usize>();
const IS_64_BIT: bool = WORD_SIZE == 8;
fn main() {
println!("=== КОНФИГУРАЦИЯ СБОРКИ ===");
println!("Уровень логирования: {}", LOG_LEVEL);
println!("Размер буфера: {} байт", BUFFER_SIZE);
println!("Профилирование включено: {}", ENABLE_PROFILING);
println!("Метрики включены: {}", ENABLE_METRICS);
println!("Трейсинг включён: {}", ENABLE_TRACING);
println!("\n=== ПЛАТФОРМА ===");
println!("Операционная система: {}", std::env::consts::OS);
println!("Архитектура: {}", std::env::consts::ARCH);
println!("Максимальная длина пути: {}", MAX_PATH_LENGTH);
println!("Разделитель путей: '{}'", PATH_SEPARATOR);
println!("\n=== АРХИТЕКТУРА ===");
println!("Размер слова: {} байт", WORD_SIZE);
println!("64-битная система: {}", IS_64_BIT);
println!("Порядок байтов: {}", std::env::consts::ENDIAN);
// Условная компиляция в коде
if ENABLE_PROFILING {
println!("\n[ПРОФИЛИРОВАНИЕ] Начало измерения производительности");
}
// Демонстрация работы с путями
let example_path = format!("home{}user{}documents{}file.txt",
PATH_SEPARATOR, PATH_SEPARATOR, PATH_SEPARATOR);
println!("\nПример пути: {}", example_path);
if example_path.len() > MAX_PATH_LENGTH {
println!("Предупреждение: путь превышает максимальную длину!");
}
}
Лучшие практики
1. Правильное именование
// Константы - SCREAMING_SNAKE_CASE
const MAX_BUFFER_SIZE: usize = 8192;
const DEFAULT_TIMEOUT_SECONDS: u64 = 30;
const PI: f64 = 3.14159265358979323846;
// Статические переменные - SCREAMING_SNAKE_CASE
static GLOBAL_COUNTER: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
static APPLICATION_CONFIG: &str = "production";
// Группировка связанных констант
mod http_status {
pub const OK: u16 = 200;
pub const NOT_FOUND: u16 = 404;
pub const INTERNAL_SERVER_ERROR: u16 = 500;
}
// Или использование enum для группировки
#[repr(u16)]
enum HttpStatus {
Ok = 200,
NotFound = 404,
InternalServerError = 500,
}
fn main() {
println!("HTTP OK: {}", http_status::OK);
println!("HTTP OK (enum): {}", HttpStatus::Ok as u16);
}
2. Документирование констант и статических переменных
/// Максимальный размер буфера для обработки данных.
///
/// Это значение выбрано исходя из баланса между использованием памяти
/// и производительностью. При увеличении значения улучшается
/// производительность, но растёт потребление памяти.
const MAX_BUFFER_SIZE: usize = 8192;
/// Таймаут по умолчанию для HTTP запросов в секундах.
///
/// # Примеры
///
/// ```rust
/// use std::time::Duration;
/// let timeout = Duration::from_secs(DEFAULT_HTTP_TIMEOUT);
/// ```
const DEFAULT_HTTP_TIMEOUT: u64 = 30;
/// Глобальный счётчик обработанных запросов.
///
/// Этот счётчик является потокобезопасным и может использоваться
/// из любого потока для отслеживания общего количества запросов.
///
/// # Безопасность
///
/// Использование атомарных операций гарантирует корректность
/// в многопоточной среде без дополнительной синхронизации.
static REQUEST_COUNTER: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
fn main() {
println!("Размер буфера: {}", MAX_BUFFER_SIZE);
println!("Таймаут: {} сек", DEFAULT_HTTP_TIMEOUT);
REQUEST_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
println!("Запросов обработано: {}",
REQUEST_COUNTER.load(std::sync::atomic::Ordering::Relaxed));
}
3. Избегание магических чисел
// ❌ Плохо: магические числа
fn bad_example() {
let buffer = vec![0u8; 4096]; // Что означает 4096?
std::thread::sleep(std::time::Duration::from_millis(5000)); // Что за 5000?
if buffer.len() > 8192 { // И что это за 8192?
println!("Буфер слишком большой");
}
}
// ✅ Хорошо: осмысленные константы
const DEFAULT_BUFFER_SIZE: usize = 4096; // 4KB - стандартный размер страницы
const NETWORK_TIMEOUT_MS: u64 = 5000; // 5 секунд таймаута сети
const MAX_SAFE_BUFFER_SIZE: usize = 8192; // 8KB - максимальный безопасный размер
fn good_example() {
let buffer = vec![0u8; DEFAULT_BUFFER_SIZE];
std::thread::sleep(std::time::Duration::from_millis(NETWORK_TIMEOUT_MS));
if buffer.len() > MAX_SAFE_BUFFER_SIZE {
println!("Буфер превышает максимальный безопасный размер");
}
}
fn main() {
bad_example();
good_example();
}
Заключение
В этой главе мы детально изучили константы и статические переменные в Rust:
✅ Константы (const) — вычисляются во время компиляции, встраиваются в код ✅ Статические переменные (static) — существуют весь жизненный цикл программы ✅ Различия между const и static — когда что использовать ✅ Мутабельные статические переменные — небезопасность и альтернативы ✅ Ленивую инициализацию — OnceLock, lazy_static, Once ✅ Константные функции (const fn) — вычисления во время компиляции ✅ Практические примеры — конфигурация, метрики, условная компиляция ✅ Лучшие практики — именование, документирование, избегание магических чисел
Правильное использование констант и статических переменных делает код более читаемым, производительным и безопасным. Они являются важными инструментами для создания надёжного системного программного обеспечения на Rust.
В следующей главе: "Комментарии в коде" — мы изучим различные способы документирования кода в Rust, включая обычные комментарии и документационные комментарии.
Практические задания
-
Создайте систему конфигурации игры с константами для различных параметров (здоровье, урон, скорость) и статическими переменными для глобального состояния
-
Реализуйте систему логирования с использованием статических переменных для уровня логирования и константных функций для форматирования сообщений
-
Напишите библиотеку математических констант с использованием const fn для вычисления значений во время компиляции
-
Создайте конфигуратор приложения, который использует ленивую инициализацию для загрузки настроек из файла или переменных окружения
Вопросы для самопроверки
- В чём основное различие между const и static переменными?
- Почему мутабельные статические переменные требуют unsafe?
- Что такое const fn и какие ограничения у них есть?
- Когда следует использовать ленивую инициализацию статических переменных?
- Как правильно организовать константы в больших проектах?